use crate::mir::{self, function::func, instr::Operand, mir_ref::{BasicBlockRef, FunctionRef, InstrRef, MIRSlotMap}};
use derive_new::new;
use std::fmt::{Write};

#[derive(Debug, new)]
pub struct MirDumper<'a> {
    pub mir_arena: &'a MIRSlotMap,
    pub funcs: &'a Vec<FunctionRef>,
}

impl<'a> MirDumper<'a> {
    fn indent(&self, level: usize, f: &mut impl Write) {
        for _ in 0..level {
            write!(f, "    ").unwrap(); // 4 个空格缩进
        }
    }

    fn new_line(&self, f: &mut impl Write) {
        write!(f, "\n").unwrap();
    }

    pub fn dump_mir(&self) -> String {
        let mut buffer = String::new();

        for func in self.funcs {
            self.dump_fn(func, &mut buffer);
        }

        buffer
    }

    pub fn dump_fn(&self, fr: &'a FunctionRef, f: &mut impl Write) {
        let func = self.mir_arena.get_func(*fr).unwrap();

        write!(f, "fn {}(", func.func_name.as_str()).unwrap();

        // dump parameters

        for (i, arg) in func.func_f_params.iter().enumerate() {
            if i != 0 {
                write!(f, " _{}: {}", i, arg.1).unwrap();
            } else {
                write!(f, "_{}: {}", i, arg.1).unwrap();
            }
        }

        write!(f, ") -> {} {{\n", func.func_ret_ty).unwrap();

        for (i, arg) in func.func_f_params.iter().enumerate() {
            self.indent(1, f);
            write!(f, "debug {} => _{};\n", arg.0, i).unwrap();
        }

        if !func.func_f_params.is_empty() {
            self.new_line(f);
        }
        // write!(f, "bbs").unwrap();
        // need to dump bbs

        for (i, bbr) in func.bbs.iter().enumerate() {
            self.dump_bb(i == func.bbs.len()-1, bbr, f);
        }

        write!(f, "}}").unwrap()
    }

    pub fn dump_bb(&self, is_last: bool, bbr: &'a BasicBlockRef, f: &mut impl Write) {
        // writeln!(f, "{:?}", bbr).unwrap();

        self.indent(1, f);
        let bb = self.mir_arena.get_bb(*bbr).unwrap();
        write!(f, "bb_{}: {{\n", bb.bbID, ).unwrap();

        for i in &bb.instrs {
            self.dump_instr(i, f);
        }


        if is_last {
            self.indent(2, f);
            writeln!(f, "return;").unwrap();
        }

        self.indent(1, f);
        write!(f, "}}\n").unwrap();
        if !is_last {
            self.new_line(f);
        }
    }

    fn parse_operand(&self, opd: Operand) -> String {
        match opd {
            mir::instr::Operand::ConstValue(cv) => cv.to_string(),
            mir::instr::Operand::Vreg(v) => {
                let mut buf = String::new();
                let i = self.mir_arena.get_instr(v).unwrap();
                match &i.vreg_id {
                    Some(v) => {
                        write!(buf, "_{}", v.vreg_id).unwrap();
                    }
                    None => {}
                };
                buf
            }
            
        }
    }

    pub fn dump_instr(&self, instr: &'a InstrRef, f: &mut impl Write) {
        let instr = self.mir_arena.get_instr(*instr).unwrap();
        // writeln!(f, "{:?}", instr).unwrap();
        self.indent(2, f);

        match &instr.instr_kind {
                // mir::instr::MirInstKind::ReturnInstr(i) => {
                //     let ret_val = i.ret_val;
                //     match ret_val {
                //         Some(v) => {
                //                     write!(f, "_0 = {}", c).unwrap();
                //         }
                //         None => {}
                //     }
                // }
                mir::instr::MirInstKind::BinInst(i) => { 
                    let lhs = self.parse_operand(i.lhs);
                    let rhs = self.parse_operand(i.rhs);
                    write!(f, "_{} = {}({}, {})", instr.get_vreg_id(), i.op, lhs, rhs).unwrap();
                }

                mir::instr::MirInstKind::SwitchInstr(i) => { 
                    let tb = self.mir_arena.get_bb(i.tureBB).unwrap();
                    let fb = self.mir_arena.get_bb(i.otherwiseBB.unwrap()).unwrap();
                    write!(f, "switchInt({}) -> [1: bb{}, otherwise: bb{}];", self.parse_operand(i.cond), tb.bbID, fb.bbID ).unwrap();
                }
                mir::instr::MirInstKind::GotoInstr(i) => { 
                    let dst = self.mir_arena.get_bb(i.dst).unwrap();
                    write!(f, "goto -> bb{}", dst.bbID).unwrap();
                }
                _ => {}
        };

        write!(f, "\n");

    }
}
